//
//  SocketPacket.m
//  Socket.IO-Client
//
//  Created by alanwang.dev on 16/8/8.
//  Copyright © 2016年 com.alanwang.dev. All rights reserved.
//

#import "SocketPacket.h"

@interface PacketDict : NSObject
@property (nonatomic, strong) NSArray *parsedData;
@property (nonatomic, strong) NSMutableArray *binary;
@end

@implementation PacketDict

- (id) init {
  if (self = [super init]) {
    _binary = [NSMutableArray array];
  }
  return self;
}

@end

@interface SocketPacket ()

@property (nonatomic, assign) NSInteger placeholders;

@property (nonatomic, strong) NSString *logType;

@end

@implementation SocketPacket

- (instancetype)initWithType:(PacketType)type data:(NSArray *)data id:(NSInteger)id nsp:(NSString *)nsp placeholders:(NSInteger)placeholders binary:(NSArray<NSData *> *)binary{
    if (self = [super init]){
        if (!data){
            self.data = [NSMutableArray array];
        }else{
            self.data = [NSMutableArray arrayWithArray:data];
        }
        self.id = id;
        self.nsp = nsp;
        self.type = type;
        self.placeholders = placeholders;
        
        if (!binary){
            self.binary = [NSMutableArray array];
        }else{
            self.binary = [NSMutableArray arrayWithArray:binary];
        }
    }
    return self;
}

- (BOOL)addData:(NSData *)data {
  if (_placeholders == _binary.count) {
    return YES;
  }
  [_binary addObject:data];
  if (_placeholders == _binary.count) {
    [self fillInPlaceholders];
    return YES;
  } else {
    return NO;
  }
}


- (NSString *)event {
  return _data[0];
}

#pragma mark - private method
- (NSString *) completeMessage:(NSString *)message {
  NSString *restOfMessage;
  if (_data.count == 0) {
    return  [NSString stringWithFormat:@"%@[]",message];
  }
  NSError *err;
  //  ??
  @try {
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:_data options:0 error:&err];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    restOfMessage = jsonString;
  } @catch (NSException *exception) {
    restOfMessage = @"[]";
  } @finally {
    
  }
  return [message stringByAppendingString:restOfMessage];
}

- (NSString *)createAck{
  NSString *message = nil;
  if (_type == Ack) {
    if ([_nsp isEqualToString:@"/"]) {
      message = [NSString stringWithFormat:@"3%@",@(_id)];
    } else {
      message = [NSString stringWithFormat:@"3%@,@%@",_nsp, @(_id)];
    }
  } else {
    if ([_nsp isEqualToString:@"/"]) {
      message = [NSString stringWithFormat:@"6%@-%@",@(_binary.count), @(_id)];
    } else {
      message = [NSString stringWithFormat:@"6%@-%@,%@",@(_binary.count), _nsp,@(_id)];
    }
  }
  return [self completeMessage: message];
}

- (NSString *)createMessageForEvent{
  NSString * message = nil;
  
  if (_type == Event) {
    if ([_nsp isEqualToString:@"/"]) {
      if (_id == -1) {
        message = @"2";
      } else {
        message = [NSString stringWithFormat:@"2%@,%@",_nsp,@(_id)];
      }
    } else {
      if (_id == -1) {
        message = [NSString stringWithFormat:@"2%@,",_nsp];
      } else {
        message = [NSString stringWithFormat:@"2%@,%@",_nsp,@(_id)];
      }
    }
  } else {
    if ([_nsp isEqualToString:@"/"]) {
      if (_id == -1) {
        message = [NSString stringWithFormat:@"5%@-",@(_binary.count)];
      } else {
        message = [NSString stringWithFormat:@"5%@-%@",@(_binary.count),@(_id)];
      }
    } else {
      if (_id == -1) {
        message = [NSString stringWithFormat:@"5%@-%@,",@(_binary.count),_nsp];
      } else {
        message = [NSString stringWithFormat:@"5%@-%@,%@",@(_binary.count),_nsp,@(_id)];
      }
    }
  }
  return [self completeMessage:message];
}

- (instancetype) initWithType:(PacketType)type nsp:(NSString *)nsp {
  if (self = [super init]) {
    _type = type;
    _nsp = nsp;
    _id = -1;
    _data = [NSMutableArray array];
    _binary = [NSMutableArray array];
  }
  return self;
}

- (NSString *)createPacketString{
  if (_type == Event || _type == BinaryEvent) {
    return [self createMessageForEvent];
  } else {
    return [self createAck];
  }
}

- (NSString *) packetString {
  return [self createPacketString];
}
// Called when we have all the binary data for a packet
// calls _fillInPlaceholders, which replaces placeholders with the
// corresponding binary
- (void)fillInPlaceholders {
  self.data =  [self _fillInPlaceholders:_data];
}

// Helper method that looks for placeholders
// If object is a collection it will recurse
// Returns the object if it is not a placeholder or the corresponding
// binary data
- (id)_fillInPlaceholders:(id)object{
  if ([object isKindOfClass:[NSDictionary class]]) {
    if ([object[@"_placeholder"]boolValue] == YES) {
      return _binary[[object[@"_placeholder"] intValue]];
    } else {
      NSMutableDictionary *dic = [NSMutableDictionary dictionary];
      [object enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        [dic setObject:[self _fillInPlaceholders:object] forKey:key];
      }];
      return dic;
    }
  } else if ([object isKindOfClass:[NSArray class]]) {
    NSMutableArray *array = [NSMutableArray array];
    [object enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      [array addObject:[self _fillInPlaceholders:object]];
    }];
    return array;
  } else {
    return object;
  }
}


// extension
+ (PacketType) findType:(NSInteger)binCount ack:(BOOL)ack {
  switch (binCount) {
    case 0:
      if (ack) {
        return Ack;
      } else {
        return Event;
      }
      break;
    default:
      if (!ack) {
        return BinaryEvent;
      } else {
        return BinaryAck;
      }
      break;
  }
}

+ (instancetype)packetFromEmit:(NSArray *)items id:(NSInteger)id nsp:(NSString *)nsp ack:(BOOL)ack{
    PacketDict *dict = [self deconstructData:items];
    SocketPacket *packet = nil;
    packet = [[SocketPacket alloc] initWithType:[self findType:dict.binary.count ack:ack] data:dict.parsedData id:id nsp:nsp placeholders:0 binary:dict.binary];
    return packet;
}

// private extension
// Recursive function that looks for NSData in collections
+ (id)shred:(id)data binary:(NSMutableArray<NSData *> *)binary{
  NSDictionary *placeHolders = @{@"_placeholder": @(YES), @"num": @(binary.count)};
  if ([data isKindOfClass:[NSData class]]) {
    [binary addObject:data];
    return placeHolders;
  } else if ([data isKindOfClass:[NSArray class]]) {
    NSMutableArray *array = [NSMutableArray array];
    [data enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      [array addObject:[self shred:obj binary:binary]];
    }];
    return array;
  } else if ([data isKindOfClass:[NSDictionary class]]) {
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    [data enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
//      [dic setObject:key forKey:[self shred:obj binary:binary]];
      [dic setObject:[self shred:obj binary:binary] forKey:key];
    }];
    return dic;
  } else {
    return  data;
  }
}

// Removes binary data from emit data
// Returns a type containing the de-binaryed data and the binary
// TODO: 返回值为([AnyObject], [NSData])
+ (PacketDict *)deconstructData:(NSArray *)data {
    PacketDict *dic = [[PacketDict alloc] init];
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:data.count];
    [data enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      [array addObject:[SocketPacket shred:obj binary:dic.binary]];
    }];
  dic.parsedData = array;
  return dic;

}


#pragma mark - lazy
- (NSMutableArray *)args{
    if (_type == Event || (_type == BinaryEvent && _data.count != 0)) {
        [_data removeObject:[_data firstObject]];
        return _data;
    }else{
        return _data;
    }
}


@end
